package com.zws.cucumber.wiremock.transformer;

import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;
import com.github.tomakehurst.wiremock.common.FileSource;
import com.github.tomakehurst.wiremock.common.Json;
import com.github.tomakehurst.wiremock.extension.Parameters;
import com.github.tomakehurst.wiremock.extension.ResponseDefinitionTransformer;
import com.github.tomakehurst.wiremock.http.Request;
import com.github.tomakehurst.wiremock.http.ResponseDefinition;
import org.apache.commons.collections4.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

import java.util.ArrayList;
import java.util.Objects;

public class ZwsJsonBodyUnquoteValueResponseTransformer extends ResponseDefinitionTransformer {

    private static final Logger logger = LoggerFactory.getLogger(ZwsJsonBodyUnquoteValueResponseTransformer.class);

    public static final String NAME = "response-unquote";

    @Override
    public ResponseDefinition transform(Request request, ResponseDefinition responseDefinition, FileSource files, Parameters parameters) {

        JsonNode jsonNode = responseDefinition.getJsonBody();
        if (jsonNode == null) {
            return responseDefinition;
        }

        if (MapUtils.isEmpty(parameters)) {
            return responseDefinition;
        }

        boolean allStringValue = parameters
                .values()
                .stream()
                .allMatch(v -> v instanceof String);
        if (allStringValue) {
            return responseDefinition;
        }

        logger.debug("before unquoted: {}", Json.write(jsonNode));
        logger.debug("before unquoted: {}", Json.write(responseDefinition.getTextBody()));

        traverseAndModifyJsonNode(jsonNode, parameters);

        logger.debug("after unquoted: {}", Json.write(jsonNode));

        ResponseDefinitionBuilder newResponseDefBuilder = ResponseDefinitionBuilder.like(responseDefinition);
        newResponseDefBuilder.withJsonBody(jsonNode);
        newResponseDefBuilder.withBodyFile(null);
        newResponseDefBuilder.withBody((String) null);
        return newResponseDefBuilder.build();
    }

    @Override
    public String getName() {
        return NAME;
    }

    @Override
    public boolean applyGlobally() {
        return false;
    }

    private void traverseAndModifyJsonNode(JsonNode node, Parameters parameters) {
        if (node.isObject()) {
            var objectNode = (ObjectNode) node;
            var fieldNames = new ArrayList<String>();
            objectNode.fieldNames().forEachRemaining(fieldNames::add);
            for (String fieldName : fieldNames) {
                var n = objectNode.get(fieldName);
                if (n.isTextual()) {
                    var v = n.asText("");
                    if (v.startsWith("{{parameters.") && v.charAt(v.length() - 3) != '}' && v.endsWith("}}")) {
                        modifyFieldIfNeeded(objectNode, fieldName, parameters, getParameterName(v, 2));

                    } else if (v.startsWith("{{{parameters.") && v.charAt(v.length() - 4) != '}' && v.endsWith("}}}")) {
                        modifyFieldIfNeeded(objectNode, fieldName, parameters, getParameterName(v, 3));

                    }

                } else if (n.isObject() || n.isArray()) {
                    traverseAndModifyJsonNode(n, parameters);

                }
            }
        } else if (node.isArray()) {
            var arrayNode = (ArrayNode) node;
            for (int i = 0; i < arrayNode.size(); i++) {
                var n = arrayNode.get(i);
                if (n.isTextual()) {
                    var v = n.asText("");
                    if (v.startsWith("{{parameters.") && v.charAt(v.length() - 3) != '}' && v.endsWith("}}")) {
                        modifyFieldIfNeeded(arrayNode, i, parameters, getParameterName(v, 2));

                    } else if (v.startsWith("{{{parameters.") && v.charAt(v.length() - 4) != '}' && v.endsWith("}}}")) {
                        modifyFieldIfNeeded(arrayNode, i, parameters, getParameterName(v, 3));

                    }

                } else if (n.isObject() || n.isArray()) {
                    traverseAndModifyJsonNode(n, parameters);

                }
            }
        }
    }

    private String getParameterName(String value, int len) {
        return value.substring(len + "parameters.".length(), value.length() - len);
    }

    private void modifyFieldIfNeeded(ObjectNode objectNode, String fieldName, Parameters parameters, String parameterName) {
        if (parameters.containsKey(parameterName)) {
            Object value = parameters.get(parameterName);

            if (Objects.isNull(value)) {
                objectNode.putNull(fieldName);

            } else if (!(value instanceof String)) {
                objectNode.set(fieldName, toJsonNode(value));
            }
        }
    }

    private void modifyFieldIfNeeded(ArrayNode arrayNode, int index, Parameters parameters, String parameterName) {
        if (parameters.containsKey(parameterName)) {
            Object value = parameters.get(parameterName);

            if (Objects.isNull(value)) {
                arrayNode.set(index, NullNode.getInstance());

            } else if (!(value instanceof String)) {
                arrayNode.set(index, toJsonNode(value));
            }
        }
    }

    private static final ObjectMapper objectMapper = new ObjectMapper();

    private <T extends JsonNode> T toJsonNode(Object value) {
        return objectMapper.valueToTree(value);
    }

}
